package org.javamap;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.javamap.overlays.*;

/**
  * Stores higher level information about the root hexmap, including rows of
  *  hexes.
  */
  
public class HexMap implements java.io.Serializable
{
	public HexMap()
	{
		this(0,0,null);
	}
	
	public HexMap(int nRows, int nCols, String defaultKey)
	{
		rows = new ArrayList<ArrayList<Hex> >(nRows);
		for (int ii = 0; ii < nRows; ++ii) {
			ArrayList<Hex> row = new ArrayList<Hex>(nCols);
			for (int iii = 0; iii < nCols; ++iii) {
				Hex h = new Hex();
				h.setImageKey(defaultKey);
				row.add(h);
			}
			rows.add(row);
		}
//		grayHex.setImageKey("unknown");
//		grayHex.setImmutable(true);
	}
	
	/**
	  * Return a hex at the desired row, col, and depth, creating hexes 
	  *  as necessary.  Starts at base and works down to desired depth.
	  */
	public Hex createHexes(int row, int col, int depth)
	{
		/* Algorithm:
			1) Calls getHex(row, col, depth), returns if non-null.
			2) Calls createHexes on what would be its parent row/col.
			3) Creates a hex for the child.
		*/
		Hex result = getHex(row, col, depth);
		if (result != null /*grayHex*/) return result;

System.err.println("createHexes("+row+','+col+','+depth+")");		
		
		// Otherwise, obtain parent:
		int parentRow = row/Hex.NCHILDPER,
			parentCol = col/Hex.NCHILDPER;
			
		Hex parent = createHexes(parentRow, parentCol, depth-1);
		// Create the child hex:
		return parent.createHex(row % Hex.NCHILDPER, col % Hex.NCHILDPER);
	}
		
	public Hex getHex(int row, int col, int depth)
	{ return getHex(row, col, depth, null); }
	/**
	  * Row 20, depth 2. NR = 6
	  * Rows: D0 = 1, D1 = 5
	  */
	public Hex getHex(int row, int col, int depth, Hex[] deepest)
	{
		if (deepest != null) deepest[0] = null;
		if (depth == 0) return getHex(row, col);
		/*
		Hex h = getHex(row/(getNRows()*Hex.depthArray[depth]), 
			col/(getNCols()*Hex.depthArray[depth])).getHex(
			row%Hex.NCHILDPER, col%Hex.NCHILDPER, depth-1); */
		int dfac = Hex.depthArray[depth];
		Hex root = getHex(row/dfac, col/dfac);
		//Hex h = root.getHex(row%dfac, col%dfac, depth - 1, deepest);
		Hex h = root.getHex(row, col, depth - 1, deepest);
		return h;
	}
	
	public int getNRows() { return rows.size(); }
	public int getNCols()
	{
		if (rows.size() == 0) return 0;
		return ((ArrayList) rows.get(0)).size();
	}
	public int getNRows(int depth) { return getNRows()*Hex.depthArray[depth]; }
	public int getNCols(int depth) { return getNCols()*Hex.depthArray[depth]; }
/*	
	public River getRiver(int index) { return (River) rivers.get(index); }
//	public List getRivers() { return rivers; }
	public int getRiverCount() { return rivers.size(); }
	public void addRiver(River r)
	{
		rivers.add(r);
		notifyAdded("River", rivers, rivers.size() - 1);
	}
	public River removeRiver(int index)
	{
		River removed = rivers.get(index);
		rivers.remove(index);
		notifyRemoved("River", rivers, removed);
		return removed;
	}
	public void addOverlay(Overlay o)
	{
		List list;
		
		if (o instanceof River) list = rivers;
		else if (o instanceof Road) list = roads;
		else throw new RuntimeException("Overlay type not found.");
*/


	public void addOverlay(Overlay o)
	{
		String type = o.getClass().getSimpleName();
		List<Overlay> list = overlayListMap.get(type);
		if (list == null)
			overlayListMap.put(type, list = new ArrayList<Overlay>());
		list.add(o);
		
		notifyAdded(type, list, list.size() - 1);
	}
	
	public int getOverlayCount(String type)
	{
		return overlayListMap.get(type).size();
	}
	
	public Overlay getOverlay(String type, int index)
	{
		return overlayListMap.get(type).get(index);
	}
	
	public boolean removeOverlay(String type, Overlay o)
	{
		return overlayListMap.get(type).remove(o);
	}
	
	/**
	  * Generic overlay removal via OverlayIterator.
	  */
	public boolean removeOverlay(Overlay o)
	{
		for (Iterator<Overlay> iter = iterateOverlays(); iter.hasNext(); ) {
			Overlay that = iter.next();
			if (that == o) {
				iter.remove();
				return true;
			}
		}
		
		return false;
	}
	
	public void addOverlayListener(OverlayListener ol)
	{ overlayListeners.add(ol); }
	
	public Iterator<Overlay> iterateOverlays()
	{
		return new OverlayIterator();
	}
	
//*****************************  Private Methods  ***************************//
	/**
	  * Get a hex from the root layer.
	  */
	private Hex getHex(int row, int col)
	{
		return (Hex) ((ArrayList) rows.get(row)).get(col);
	}
	
	public void notifyAdded(String type, List overlayList, int index)
	{
		for (int ii = 0; ii < overlayListeners.size(); ++ii)
			overlayListeners.get(ii).overlayAdded(
					this,
					type,
					overlayList,
					index
			);
	}
	
	public void notifyRemoved(String type, List overlayList, Overlay removed)
	{
		for (int ii = 0; ii < overlayListeners.size(); ++ii)
			overlayListeners.get(ii).overlayRemoved(
					this,
					type,
					overlayList,
					removed
			);
	}
	
	private void readObject(java.io.ObjectInputStream in)
		throws java.io.IOException, ClassNotFoundException
	 {
		 in.defaultReadObject();
		 overlayListeners = new ArrayList<OverlayListener>();
	 } 
	
//****************************  Inner Classes  ******************************//
	private class OverlayIterator implements Iterator<Overlay>
	{	
		public boolean hasNext()
		{
/*			while (!iter.hasNext()) {
				if (++listIndex == lists.length) return false;
				iter = (Iterator<Overlay>) lists[listIndex].iterator();
			}
			return true; */
			
			while (true) {
				if (currList == null) {
					if (!listIter.hasNext()) return false;
					currList = listIter.next();
					iter = currList.iterator();
				}
				if (iter.hasNext()) return true;
				else currList = null;
			}			
		}
		
		public Overlay next()
		{
			if (!hasNext()) throw new java.util.NoSuchElementException();
			return lastNext = iter.next();
		}
		
		public void remove()
		{ 
			iter.remove();
			notifyRemoved(
					lastNext.getClass().getSimpleName(),
					//lists[listIndex],
					currList,
					lastNext
			);
			lastNext = null;
		}
		
		//private final java.util.List[] lists = { rivers, roads };
		//private int listIndex = 0;
		//private Iterator<Overlay> iter = (Iterator<Overlay>) lists[0].iterator();
		
		private Iterator<List<Overlay> > listIter 
				= overlayListMap.values().iterator();
		private List<Overlay> currList;
		private Iterator<Overlay> iter;
		private Overlay lastNext;
	}

//****************************  Data Members  *******************************//
	/**
      *  Lists of rows, with each row a list of hexes in that row.
	  */
	private final ArrayList<ArrayList<Hex> > rows;
	/**
	  * A list of river overlays.
	  */
	//private final ArrayList<River> rivers = new ArrayList<River>();
	//private final ArrayList<Road> roads = new ArrayList<Road>();
	private final HashMap<String, List<Overlay> > overlayListMap 
			= new HashMap<String, List<Overlay> >();
		
	private transient ArrayList<OverlayListener> overlayListeners 
			= new ArrayList<OverlayListener>();
	
//******************************  Statics  **********************************//
}
